﻿@page "/Demo_FileUpload"

@code{
	const string UPLOAD_TOAST_KEY = "myuploadprogress";


	System.IO.Stream _streamtosave = null;


	async Task UploadHandler_Toast(BlazorUploaderFile file)
	{
		System.IO.Stream targetstream = _streamtosave;
		_streamtosave = null;

		long readok = 0;
		byte[] buffer = new byte[1024 * 16];
		while (true)
		{
			double p = Math.Floor(100 * (double)readok / (double)file.FileSize);
			BlazorSession.Current.Toast(file.FileName + " " + p + "% (" + readok / 1024 + "KB/" + file.FileSize / 1024 + "KB)", 5000, UPLOAD_TOAST_KEY);
			int rc = await file.Stream.ReadAsync(buffer, 0, buffer.Length);
			if (rc == 0)
				break;
			readok += rc;
			if (readok > file.FileSize)
				throw new Exception("Invalid stream size");

			if (targetstream != null)
				await targetstream.WriteAsync(buffer, 0, rc);

			await Task.Delay(100);//control the upload speed
		}
		if (readok < file.FileSize)
			throw new Exception("file upload break");

		BlazorSession.Current.Toast(file.FileName + " DONE (" + readok / 1024 + "KB) " + file.FileMime, 2000, UPLOAD_TOAST_KEY);
	}

	async Task UploadHandler_Dialog(BlazorUploaderFile file)
	{
		System.IO.Stream targetstream = _streamtosave;
		_streamtosave = null;

		BlazorDialog dlg = null;

		long readok = 0;
		bool cancelclicked = false;
		System.Threading.CancellationTokenSource quitsignal = new System.Threading.CancellationTokenSource();

		void DoCancel()
		{
			cancelclicked = true;
			dlg.Close();
		}

		RenderFragment rf = (__builder) =>
		{
			double p = Math.Floor(100 * (double)readok / (double)file.FileSize);

		<div style="padding:15px;border:solid 1px #eee;box-shadow:0px 0px 6px gray;background-color:#fff;min-width:280px;text-align:center;">
			<div>@file.FileName</div>
			<div>@file.FileMime</div>
			<div>@p% @(readok/1024)KB / @(file.FileSize/1024)KB</div>
			<div style="text-align:left;background:#eee;height:5px;position:relative;margin:3px;"><span style="position:absolute;width:@p%;display:inline-block;height:100%;background-color:darkgreen;"></span></div>
			<div style="text-align:center"><button @onclick="DoCancel">Cancel</button></div>
		</div>
		};
		async Task RunDialogAsync()
		{
			await Task.Delay(10);
			try
			{

				byte[] buffer = new byte[1024 * 16];
				while (true)
				{
					double p = Math.Floor(100 * (double)readok / (double)file.FileSize);

					int rc = await file.Stream.ReadAsync(buffer, 0, buffer.Length);
					if (rc == 0)
						break;

					if (cancelclicked)
						break;

					readok += rc;
					if (readok > file.FileSize)
						throw new Exception("Invalid stream size");


					if (targetstream != null)
						await targetstream.WriteAsync(buffer, 0, rc);

					dlg.StateHasChanged();

					await Task.Delay(100);
				}
				if (readok < file.FileSize)
					throw new Exception("file upload break");

				BlazorSession.Current.Toast(file.FileName + " DONE (" + readok / 1024 + "KB) " + file.FileMime, 2000, UPLOAD_TOAST_KEY);
			}
			catch (Exception x)
			{
				BlazorSession.Current.ConsoleError(x.ToString());
			}
			finally
			{
				quitsignal.Cancel();
				if (!cancelclicked) dlg.Close();
			}


			BlazorSession.Current.ConsoleLog("RunDialogAsync END");
		}
		void DialogInit(BlazorDialog argdlg)
		{
			dlg = argdlg;
			Task t = RunDialogAsync();
		}


		BlazorSession.Current.ShowDialog(rf, DialogInit);

		try
		{
			//must wait until quitsignal be set , otherwise the file will be disposed*
			await Task.Delay(-1, quitsignal.Token);
		}
		catch (TaskCanceledException)
		{

		}
	}
}

<h1>BlazorUploader</h1>
<p>
	The BlazorUploader component helps developer to streaming the upload data easily.
</p>
<p style="color:red">
	In this sample , the upload speed is limited
</p>

<fieldset>
	<legend>Simple Uploader</legend>
	<BlazorUploader @ref="uploader1" />
</fieldset>

@code
{
	BlazorUploader __uploader1;
	BlazorUploader uploader1
	{
		get
		{
			return __uploader1;
		}
		set
		{
			__uploader1 = value;
			if (__uploader1 == null) return;
			__uploader1.ProcessSingleUploadAsync = UploadHandler_Toast;
		}
	}


}


<br />
<fieldset>
	<legend>As Transparent Mask , opacity:0 </legend>
	@* IE11 doesn't support input in the button , so the onclick fix it *@
	<button class="btn btn-info" style="display:inline-block;position:relative;" onclick="this.querySelector('input').click()">
		<span>Upload a file</span>
		<BlazorUploader @ref="uploader2" style="position:absolute;left:0px;top:0px;width:100%;height:100%;opacity:0" />
	</button>

</fieldset>

@code
{
	BlazorUploader __uploader2;
	BlazorUploader uploader2
	{
		get
		{
			return __uploader2;
		}
		set
		{
			__uploader2 = value;
			if (__uploader2 == null) return;
			__uploader2.ProcessSingleUploadAsync = UploadHandler_Toast;
		}
	}
}


<br />
<fieldset>
	<legend>Progress dialog with cancel button</legend>

	<button style="display:inline-block;position:relative;" onclick="this.querySelector('input').click()">
		<span>Upload a file</span>
		<BlazorUploader @ref="uploader3" style="position:absolute;left:0px;top:0px;width:100%;height:100%;opacity:0" />
	</button>

</fieldset>

@code
{
	BlazorUploader __uploader3;
	BlazorUploader uploader3
	{
		get
		{
			return __uploader3;
		}
		set
		{
			__uploader3 = value;
			if (__uploader3 == null) return;
			__uploader3.ProcessSingleUploadAsync = UploadHandler_Dialog;
		}
	}
}


<br />
<fieldset>
	<legend>JavaScript Blob Upload API</legend>

	<button style="display:inline-block;position:relative;" onclick="
	var uploader = this.querySelector('input');
	var arr = [document.documentElement.outerHTML];
	for (var i = 0; i != 10; i++)
		arr = arr.concat(arr);
	var file = new Blob(arr, {type:'text/html'});
	file.name = 'theFileIsCreatedByJavaScript.htm';
	uploader.postfiles([file]);
	">
		<span>Generate file and upload</span>
		<BlazorUploader @ref="uploader4" style="position:absolute;left:0px;top:0px;width:0px;opacity:0" />
	</button>

</fieldset>

@code
{
	BlazorUploader __uploader4;
	BlazorUploader uploader4
	{
		get
		{
			return __uploader4;
		}
		set
		{
			__uploader4 = value;
			if (__uploader4 == null) return;
			__uploader4.ProcessSingleUploadAsync = UploadHandler_Dialog;
		}
	}
}




<br />
<fieldset>
	<legend>Check Upload Parameter (Image file &lt; 200K) </legend>

	<button style="display:inline-block;position:relative;" onclick="this.querySelector('input').click()">
		<span>Upload a file</span>
		<BlazorUploader @ref="uploader5" style="position:absolute;left:0px;top:0px;width:100%;height:100%;opacity:0" />
	</button>

</fieldset>

@code
{
	bool IsFileOK(BlazorUploaderFile file)
	{
		if (file.FileSize > 1024 * 200)
			return false;
		if (!file.FileMime.StartsWith("image/"))
			return false;
		return true;
	}

	BlazorUploader __uploader5;
	BlazorUploader uploader5
	{
		get
		{
			return __uploader5;
		}
		set
		{
			__uploader5 = value;
			if (__uploader5 == null) return;
			__uploader5.ProcessSingleUploadAsync = async (file) =>
			{
			//must check it again.
			if (!IsFileOK(file))
					throw new Exception("Invalid file");

				await UploadHandler_Dialog(file);
			};
			__uploader5.ProcessQueryUploadAsync = async (files) =>
			{
				foreach (var file in files)
				{
					if (!IsFileOK(file))
					{
						file.Cancel = true;
						BlazorSession.Current.Alert("Error", "Please select another file");
					}
				}
			};
		}
	}
}




<br />
<fieldset>
	<legend>Client side pre-handler : Resize image before upload</legend>

	<button style="display:inline-block;position:relative;" onmousedown="@uploader6_onclick_script">
		<span>Upload a file</span>
		<BlazorUploader @ref="uploader6" style="position:absolute;left:0px;top:0px;width:0;opacity:0" />
	</button>

</fieldset>

@code
{
	BlazorUploader __uploader6;
	BlazorUploader uploader6
	{
		get
		{
			return __uploader6;
		}
		set
		{
			__uploader6 = value;
			if (__uploader6 == null) return;
			__uploader6.ProcessSingleUploadAsync = async (file) =>
			{

				try
				{
					System.IO.MemoryStream ms = new System.IO.MemoryStream();
					_streamtosave = ms;

					await UploadHandler_Dialog(file);

					string dataurl = "data:" + file.FileMime + ";base64," + Convert.ToBase64String(ms.ToArray());

					BlazorSession.Current.ConsoleLog("dataurl size " + dataurl.Length);

					BlazorDialog dlg = null;
					RenderFragment rf = (__builder) =>
				{

				<div style="padding:15px;border:solid 1px #eee;box-shadow:0px 0px 6px gray;background-color:#fff;min-width:280px;text-align:center;">
					<div>@file.FileName @file.FileMime @(file.FileSize/1024)KB </div>
					<div><img src="@dataurl" @onclick="() => dlg.Close()" style="max-width:90%;max-height:90%;" /></div>
				</div>
				};
					BlazorSession.Current.ShowDialog(rf, (argdlg) => { dlg = argdlg; });
				}
				catch (Exception x)
				{
					BlazorSession.Current.ConsoleError(x.ToString());
				}
			};
		}
	}

	const string uploader6_onclick_script = @"
var uploader = this.querySelector('input');
uploader.onchange = function () {
setTimeout(function () { uploader.value = ''; }, 1);
if (!uploader.files.length) return;
var file = uploader.files[0];
if (file.type.substring(0, 6) != 'image/')
return Toast('Please select a photo',2000);

var img = new Image();

img.onload = function () {
setTimeout(function(){
file=MakeNewFile(img,file)
uploader.postfiles([file]);
}, 1);
}

var fr = new FileReader();
fr.readAsDataURL(file);
fr.onload = function () {
img.src = fr.result;
}
}
uploader.click();


function MakeNewFile(img,file) {
var imgw = img.width;
var imgh = img.height;

var newfile=file;

//console.log(imgw + ':' + imgh);

var maxw = 960;
var maxh = 960;

var f = Math.max(imgw / maxw, imgh / maxh);
if (f < 1)
return file;

var neww = Math.floor(imgw / f);
var newh = Math.floor(imgh / f);
var canvas = document.createElement('canvas');
canvas.width = neww;
canvas.height = newh;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, imgw, imgh, 0, 0, neww, newh);

var dataurl = canvas.toDataURL('image/jpeg');;

var bs = atob(dataurl.split(',')[1]);
var ab = new ArrayBuffer(bs.length);
var ia = new Uint8Array(ab);
for (i = 0; i < bs.length; i += 1) {
ia[i] = bs.charCodeAt(i);
}
var newfile = new Blob([ab], { type: 'image/jpeg' });//, name: file.name, lastModified: file.lastModified, lastModifiedDate: file.lastModifiedDate
newfile.name = file.name;

console.warn( imgw+'*'+imgh+' resized to '+neww+'*'+newh);
console.warn( Math.floor(file.size/1024)+'KB resized to '+Math.floor(newfile.size/1024)+'KB');

return newfile;
}

";
}


<br />
<br />
<br />
<br />